home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Sapphire Collection / Software Vault (Sapphire Collection) (Digital Impact).ISO / cdr08 / mygroups.zip / MYGROUPS.PAS < prev    next >
Pascal/Delphi Source File  |  1994-06-29  |  39KB  |  1,104 lines

  1. { MyGroups - Enable different icons for Program Manager groups
  2.   (c) 1994 by Charles C. Edwards
  3.   First Published in PC Magazine September 27, 1994 US Edition
  4.  
  5.   This program compiles as MYGROUPS.DLL but must be renamed to
  6.   MYGROUPS.EXE prior to being run.}
  7.  
  8. {$S-,D-,L-,G-,W+,B-}
  9. Library MyGroups;
  10. {$R MyGroups.RES}
  11. {$D Copyright (c) 1994 by Charles C. Edwards}
  12. Uses WinTypes, WinProcs, WinDos, Strings, ShellAPI, CommDlg,
  13.    DDEML, DDE, CStr, GrpFile, {$IFDEF VER70} Objects; {$ELSE} WObjects; {$ENDIF}
  14.  
  15. Const Ini = 'MYGROUPS.INI';
  16.       Icon_Section =  'Icons';
  17.       Menu_Section =  'Menu';
  18.  
  19.       Warnings = 'Warnings';
  20.       WarnGrp  = 'WarnGroup';
  21.       WarnIcon = 'WarnIcon';
  22.  
  23. Type PIconRec = ^TIconRec;
  24.      TIconRec = Record
  25.                 FileName:Array [0..255] of Char;
  26.                 WindowText:Array [0..255] of Char;
  27.                 Index:Integer;
  28.                 End;
  29.  
  30. Const cm_ChangeIcon = $70;  {Change icon menu item}
  31.       cm_UnloadProg = $80;  {Unload MyGroups}
  32.  
  33. Const id_File     = 100;   {Change icon dialog box controls}
  34.       id_Icon     = 101;
  35.       id_IconBar  = 102;
  36.       id_Browse   = 112;
  37.       id_Default  = 113;
  38.       id_Programs = 114;
  39.  
  40. Const DidSubClass:Boolean = False;
  41.  
  42. Var pmIcon:hIcon;
  43.     oldGroupProc,oldPMProc:TFarProc;
  44.     ProgMan,Myself:Array [0..256] of Char;
  45.     MDIClient:hWnd;
  46.     WinVer:Word;
  47.     MyModule:THandle;
  48.     IniGroups:PChar;
  49.     IniSize:Integer;
  50.     Collection:PStrCollection;
  51.     pmDDE:PDDE;
  52.     Warn_Grp,Warn_Icon:Boolean;
  53.     CopyRight:hWnd;
  54.  
  55. {The following is a collection of PIconRec items}
  56. Type TIconCollection = Object(TCollection)
  57.         Procedure FreeItem(Item:Pointer); Virtual;
  58.         End;
  59.      PIconCollection = ^TIconCollection;
  60.  
  61. Procedure TIconCollection.FreeItem(Item:Pointer);
  62. {Free the PIconRec item in the collection
  63.  
  64.  Input:  Item - a pointer to a TIconRec}
  65.  
  66.    Begin
  67.    Dispose(PIconRec(Item));
  68.    End;
  69.  
  70. Function GroupProc(Window:hWnd; Msg,wParam:Word; lParam:LongInt):Longint;
  71.    Export; Forward;
  72.  
  73. Function PMProc(Window:hWnd; Msg,wParam:Word; lParam:LongInt):LongInt;
  74.    Export; Forward;
  75.  
  76. Function GetIconData(Window:hWnd; Var IconRec:TIconRec):Boolean;
  77. {Fills a TIconRec structure with data from MYGROUPS.INI for a group window.
  78.  Returns TRUE if data found in MYGROUPS.INI
  79.  
  80.  Input:  Window - The group window for which to return the data
  81.  Output: IconRec - Structure filled with the icon data}
  82.  
  83. Var S:String;
  84.     I:Integer;
  85.     P:PChar;
  86.  
  87.    Begin
  88.    GetWindowText(Window,IconRec.WindowText,Sizeof(IconRec.WindowText));
  89.    If GetPrivateProfileString(Icon_Section,IconRec.WindowText,'',
  90.       IconRec.FileName,Sizeof(IconRec.FileName),Ini) > 0 then
  91.       Begin
  92.       P:=StrPos(IconRec.FileName,',');
  93.       S:=StrPas(P+1);
  94.       P^:=#0;
  95.       Val(S,Iconrec.Index,I);
  96.       GetIconData:=True;
  97.       End
  98.    else
  99.       Begin
  100.       StrCopy(IconRec.FileName,ProgMan);
  101.       IconRec.Index:=7;
  102.       GetIconData:=False;
  103.       End;
  104.    End;
  105.  
  106. Procedure PutIconData(Var IconRec:TIconRec);
  107. {Writes data in a TIconRec structure to the MYGROUPS.INI file.
  108.  
  109.  Input:  IconRec - data to write to MYGROUPS.INI}
  110.  
  111. Const Buf:Array [0..255] of Char = '';
  112.  
  113. Var S:Array [0..10] of Char;
  114.  
  115.    Begin
  116.    Str(IconRec.Index,S);
  117.    StrCopy(Buf,IconRec.FileName);
  118.    StrCat(StrCat(Buf,','),S);
  119.    WritePrivateProfileString(Icon_Section,IconRec.WindowText,Buf,Ini);
  120.    End;
  121.  
  122. Function SubclassGroups(Window:hWnd; lParam:LongInt):Boolean; Export;
  123. {Subclasses the individual program groups.
  124.  Also superclasses the PMGroup class.
  125.  Always returns TRUE
  126.  
  127.  Input:  Window - The group window to be subclassed
  128.          lParam - Not used}
  129.  
  130. Const FirstMatch:Boolean = True;
  131.  
  132. Var szClassName:Array [0..255] of Char;
  133.     Msg:Array [0..255] of Char absolute szClassName;
  134.     Index:Integer;
  135.     IconRec:TIconRec;
  136.     Icon:hIcon;
  137.     I:Integer;
  138.  
  139.    Begin
  140.    GetClassName(Window,szClassName,Sizeof(szClassName));
  141.    If StrIComp(szClassName,'MDIClient') = 0 then
  142.       MDIClient:=Window;
  143.    If StrIComp(szClassName,'PMGroup') = 0 then
  144.       Begin
  145.       If FirstMatch then
  146.          Begin
  147.          oldGroupProc:=TFarProc(GetClassLong(Window,gcl_WndProc));
  148.          SetClassLong(Window,gcl_WndProc,LongInt(@GroupProc));
  149.          pmIcon:=GetClassWord(Window,gcw_hIcon);
  150.          SetClassWord(Window,gcw_hIcon,0);
  151.          FirstMatch:=False;
  152.          DidSubClass:=True;
  153.          End;
  154.       SetWindowLong(Window,gwl_WndProc,LongInt(@GroupProc));
  155.       Icon:=0;
  156.       If GetIconData(Window,IconRec) then
  157.          Begin
  158.          I:=Collection^.Count-1;
  159.          While (I >= 0) and
  160.                (StrComp(Collection^.At(I),IconRec.WindowText) <> 0) do
  161.             Dec(I);
  162.          If I >= 0 then
  163.             Collection^.AtFree(I);
  164.          Icon:=ExtractIcon(hInstance,IconRec.FileName,IconRec.Index);
  165.          If Icon < 2 then
  166.             Begin
  167.             StrCopy(Msg,'Warning: Cannot find ');
  168.             StrCat(StrCat(Msg,IconRec.FileName),^M);
  169.             StrCat(Msg,'Group ');
  170.             StrCat(StrCat(Msg,'"'),IconRec.WindowText);
  171.             StrCat(Msg,'" will use the default icon.');
  172.             If Warn_Icon then
  173.                MessageBox(0,Msg,'MyGroups Error',mb_IconExclamation or mb_OK);
  174.             Icon:=pmIcon;
  175.             End
  176.          else
  177.             Icon:=GlobalReAlloc(Icon,GlobalSize(Icon),gmem_Modify or gmem_DDEShare);
  178.          End
  179.       else
  180.          Icon:=pmIcon;
  181.       SetProp(Window,Icon_Section,Icon);
  182.       SetProp(Window,Menu_Section,0);
  183.       If IsIconic(Window) then
  184.          Begin
  185.          InvalidateRgn(Window,0,True);
  186.          UpdateWindow(Window);
  187.          End;
  188.       End;
  189.    SubclassGroups:=True;
  190.    End;
  191.  
  192. Function UnsubclassGroups(Window:hWnd; lParam:LongInt):Boolean; Export;
  193. {Removes the subclassing and superclassing performed in SubclassGroups
  194.  Always returns TRUE
  195.  
  196.  Input:  Window - The group window for which to remove subclassing
  197.          lParam - Not used}
  198.  
  199. Const FirstMatch:Boolean = True;
  200.  
  201. Var szClassName:Array [0..255] of Char;
  202.     Index:Integer;
  203.     Menu:hMenu;
  204.     IconRec:TIconRec;
  205.     Icon:hIcon;
  206.  
  207.    Begin
  208.    GetClassName(Window,szClassName,Sizeof(szClassName));
  209.    If StrIComp(szClassName,'PMGroup') = 0 then
  210.       Begin
  211.       If FirstMatch then
  212.          Begin
  213.          SetClassLong(Window,gcl_WndProc,LongInt(oldGroupProc));
  214.          SetClassWord(Window,gcw_hIcon,pmIcon);
  215.          FirstMatch:=False;
  216.          End;
  217.       SetWindowLong(Window,gwl_WndProc,LongInt(oldGroupProc));
  218.       If RemoveProp(Window,Menu_Section) = 1 then
  219.          Begin
  220.          Menu:=GetSystemMenu(Window,False);
  221.          DeleteMenu(Menu,9,mf_ByPosition);
  222.          DeleteMenu(Menu,cm_ChangeIcon,mf_ByCommand);
  223.          DeleteMenu(Menu,cm_UnloadProg,mf_ByCommand);
  224.          End;
  225.       Icon:=RemoveProp(Window,Icon_Section);
  226.       If (Icon <> 0) and (Icon <> pmIcon) then DestroyIcon(Icon);
  227.       End;
  228.    UnsubclassGroups:=True;
  229.    End;
  230.  
  231. Function EnumProc(Window:hWnd; lParam:LongInt):Boolean; Export;
  232. {Called during initialization and shut down to subclass and
  233.  and unsubclass the program groups. This function enumerates
  234.  all of the child windows for the Program Manager main window.
  235.  Returns the result of the child window enumeration if there are
  236.  any child windows, otherwise it returns TRUE.
  237.  
  238.  Input:  Window - The Program Manager top level window
  239.          lParam - 0 = Subclassing the groups
  240.                   1 = Unsubclassing the groups}
  241.  
  242. Var szClassName:Array [0..255] of Char;
  243.  
  244.    Begin
  245.    GetClassName(Window,szClassName,Sizeof(szClassName));
  246.    If (StrIComp(szClassName,'ProgMan') = 0) then
  247.       Begin
  248.       If lParam = 0 then
  249.          Begin
  250.          OldPMProc:=TFarProc(GetWindowLong(Window,gwl_WndProc));
  251.          SetWindowLong(Window,gwl_WndProc,LongInt(@PMProc));
  252.          End
  253.       else
  254.          SetWindowLong(Window,gwl_WndProc,LongInt(OldPMProc));
  255.       If lParam = 0 then
  256.          EnumProc:=EnumChildWindows(Window,@SubclassGroups,0)
  257.       else
  258.          EnumProc:=EnumChildWindows(Window,@UnsubclassGroups,0);
  259.       EnumProc:=False;
  260.       End
  261.    else
  262.       EnumProc:=True;
  263.    End;
  264.  
  265. Function CopyRight_Dlg(Dialog:hWnd; Msg,wParam:Word; lParam:LongInt):LongInt;
  266.    Export;
  267. {Dialog function for the copyright dialog box. Nothing special here,
  268.  just a plain vanilla dialog function.
  269.  Returns 1 if the message was processed.
  270.  
  271.  Input:  The standard dialog function parameters}
  272.  
  273. Var MR,WR:TRect;
  274.     NewX,NewY:Integer;
  275.  
  276.    Begin
  277.    CopyRight_Dlg:=1;
  278.    Case Msg of
  279.       wm_InitDialog:
  280.          Begin   {Center dialog box in window}
  281.          GetWindowRect(Dialog,MR);
  282.          GetWindowRect(GetDesktopWindow,WR);
  283.          OffsetRect(MR,-MR.left,-MR.top);
  284.          NewX:=WR.left+((WR.right-WR.left+1)-(MR.right+1)) div 2;
  285.          NewY:=WR.top+((WR.bottom-WR.top+1)-(MR.Bottom+1)) div 2;
  286.          MoveWindow(Dialog,NewX,NewY,MR.right+1,MR.bottom+1,False);
  287.          If lParam = 1 then
  288.             ShowWindow(GetDlgItem(Dialog,IDOK),sw_ShowNormal);
  289.          End;
  290.       wm_Command:
  291.          If wParam = IDOK then
  292.             EndDialog(Dialog,0)
  293.          else
  294.             CopyRight_Dlg:=0;
  295.       else
  296.          CopyRight_Dlg:=0;
  297.       End;
  298.    End;
  299.  
  300. Function Icon_Dialog(Window:hWnd; Msg,wParam:Word; lParam:LongInt):LongInt;
  301.    Export;
  302. {Dialog function for the "Change Icon" dialog box. Handles the loading
  303.  and drawing of icons in the listbox.
  304.  Returns 1 if the message was processed.
  305.  
  306.  Input:  The standard dialog function parameters}
  307. Const IconRec:TIconRec = (FileName:''; WindowText: ''; Index:0);
  308.       Parent:hWnd = 0;
  309.       Titl:Array [0..255] of Char = '';
  310.       IconCol:PIconCollection = Nil;
  311.       OldFile:Array [0..255] of Char = '';
  312.  
  313. Var I,J:Integer;
  314.     Icon:hIcon;
  315.     Rect:TRect;
  316.     Brush:hBrush;
  317.  
  318.    Procedure Adjust_Win30;
  319.    {This procedure adjusts the size of the listbox to deal with
  320.     an idiosyncracy of Windows 3.0}
  321.  
  322.     Var I,VisibleIcons:Word;
  323.  
  324.       Begin
  325.       GetWindowRect(GetDlgItem(Window,id_IconBar),Rect);
  326.       I:=SendDlgItemMessage(Window,id_Iconbar,lb_GetCount,0,0);
  327.       VisibleIcons:=(Rect.Right-Rect.Left) div
  328.         (GetSystemMetrics(sm_cxIcon)+4);
  329.       SetWindowPos(GetDlgItem(Window,id_IconBar),0,0,0,
  330.          Rect.Right-Rect.Left,
  331.          GetSystemMetrics(sm_cyIcon)+4+
  332.          (GetSystemMetrics(sm_cyHScroll)*Byte(I > VisibleIcons)),
  333.          swp_noZOrder or swp_NoMove);
  334.       End;
  335.  
  336.    Procedure LoadIcons(FName:PChar);
  337.    {Loads the listbox with all of the icons from the specified file.
  338.  
  339.     Input:  FName - Name of the file containing the icons}
  340.  
  341.    Const Dest:Array [0..255] of Char = '';
  342.  
  343.    Var I,VisibleIcons:Word;
  344.        Icon:hIcon;
  345.        Cursor:hCursor;
  346.  
  347.       Begin
  348.       If IconCol <> Nil then
  349.          Begin
  350.          Dispose(IconCol,Done);
  351.          IconCol:=Nil;
  352.          SetWindowText(GetDlgItem(Window,id_Programs),'&Programs');
  353.          End;
  354.       Cursor:=SetCursor(LoadCursor(0,idc_Wait));
  355.       FileExpand(Dest,FName);
  356.       SendDlgItemMessage(Window,id_IconBar,lb_ResetContent,0,0);
  357.       Icon:=ExtractIcon(hInstance,Dest,0);
  358.       If Icon < 2 then
  359.          Begin
  360.          MessageBox(Window,'There are no icons in this file.'^M+
  361.             'You may choose from the icons in the Program Manager.',
  362.             FName,mb_IconExclamation or mb_OK);
  363.          FileExpand(Dest,ProgMan);
  364.          Icon:=ExtractIcon(hInstance,Dest,0);
  365.          End;
  366.       StrCopy(OldFile,Dest);
  367.       I:=0;
  368.       Repeat
  369.          SendDlgItemMessage(Window,id_IconBar,lb_AddString,0,Word(Icon));
  370.          Inc(I);
  371.          Icon:=ExtractIcon(hInstance,Dest,I);
  372.       Until Icon < 2;
  373.       If (Lo(WinVer) = 3) and (Hi(WinVer) < 10) then
  374.          Adjust_Win30;
  375.       SendDlgItemMessage(Window,id_IconBar,lb_SetCurSel,0,0);
  376.       SetWindowText(GetDlgItem(Window,id_File),Dest);
  377.       SetCursor(Cursor);
  378.       End;
  379.  
  380.    Function Process_OK(Check_Done:Boolean):Boolean;
  381.    {This function handles the pressing of the OK button. There are 2
  382.     cases this function has to consider.
  383.     1. If the file name in the edit control was changed, then it calls
  384.        LoadIcons to put the new icons in the list box.
  385.     2. Otherwise, replace the group icon with the currently selected icon.
  386.     Returns TRUE if the group icon was changed.
  387.  
  388.     Input:  Check_Done - TRUE = If edit control not changed, update the
  389.                                 group icon.
  390.                          FLASE = If edit control not changed, do not
  391.                                  update the group icon.}
  392.  
  393.    Const FName:Array [0..255] of Char = '';
  394.  
  395.    Var I:Integer;
  396.        Icon:hIcon;
  397.        PIR:PIconRec;
  398.  
  399.       Begin
  400.       Process_OK:=False;
  401.       If SendDlgItemMessage(Window,id_File,em_GetModify,0,0) <> 0 then
  402.          Begin
  403.          GetWindowText(GetDlgItem(Window,id_File),FName,Sizeof(FName));
  404.          SendDlgItemMessage(Window,id_File,em_SetModify,0,0);
  405.          LoadIcons(FName);
  406.          Exit;
  407.          End;
  408.       If not Check_Done then Exit;
  409.       GetWindowText(GetDlgItem(Window,id_File),FName,Sizeof(FName));
  410.       I:=SendDlgItemMessage(Window,id_IconBar,lb_GetCurSel,0,0);
  411.       If I = lb_Err then
  412.          Begin
  413.          MessageBox(Window,'No icon is currently selected',FName,
  414.            mb_IconExclamation or mb_OK);
  415.          Exit;
  416.          End;
  417.       If IconCol <> Nil then
  418.          Begin
  419.          PIR:=IconCol^.At(I);
  420.          StrCopy(FName,PIR^.FileName);
  421.          I:=PIR^.Index;
  422.          End;
  423.       If GetDriveType(Ord(UpCase(FName[0]))-Ord('A')) <> DRIVE_FIXED then
  424.          If MessageBox(Window,'This drive may not be available in future Windows sessions.'+
  425.            ^M'Do you want to continue?',
  426.            FName,mb_IconQuestion or mb_YesNo) <> id_Yes then
  427.              Exit;
  428.       Icon:=RemoveProp(Parent,Icon_Section);
  429.       If (Icon <> 0) and (Icon <> pmIcon) then DestroyIcon(Icon);
  430.       StrCopy(IconRec.FileName,FName);
  431.       IconRec.Index:=I;
  432.       Icon:=ExtractIcon(hInstance,FName,I);
  433.       SetProp(Parent,Icon_Section,Icon);
  434.       InvalidateRgn(Parent,0,True);
  435.       UpdateWindow(Parent);
  436.       PutIconData(IconRec);
  437.       Process_OK:=True;
  438.       End;
  439.  
  440.    Procedure Process_Browse;
  441.    {This procedure handles the pressing of the "Browse" button.
  442.     It invokes the Common Dialog library GetOpenFileName function to
  443.     get the name of a new icon file.}
  444.  
  445.    Const Filter:PChar = 'Icon Files'#0'*.ico;*.dll;*.exe'#0+
  446.                         'Programs (*.exe)'#0'*.exe'#0+
  447.                         'Libraries (*.dll)'#0'*.dll'#0+
  448.                         'Icons (*.ico)'#0'*.ico'#0+
  449.                         'All files (*.*)'#0'*.*'#0;
  450.          Browse:PChar = 'Browse';
  451.          Buf:Array [0..127] of Char = '';
  452.  
  453.    Var ofn:TOpenFileName;
  454.  
  455.       Begin
  456.       With ofn do
  457.          Begin
  458.          lStructSize:=Sizeof(TOpenFileName);
  459.          hWndOwner:=Window;
  460.          lpstrFilter:=Filter;
  461.          lpstrCustomFilter:=Nil;
  462.          nFilterIndex:=1;
  463.          lpstrFile:=Buf;
  464.          lpstrFile[0]:=#0;
  465.          nMaxFile:=Sizeof(Buf);
  466.          lpstrFileTitle:=Nil;
  467.          lpstrInitialDir:=Nil;
  468.          lpstrTitle:=Browse;
  469.          Flags:=ofn_FileMustExist or ofn_PathMustExist or
  470.                 ofn_HideReadOnly;
  471.          lpstrDefExt:=Nil;
  472.          End;
  473.       If GetOpenFileName(ofn) then
  474.          Begin
  475.          SetWindowText(GetDlgItem(Window,id_File),Buf);
  476.          SendDlgItemMessage(Window,id_File,em_SetModify,1,0);
  477.          Process_OK(False);
  478.          End;
  479.       End;
  480.  
  481.    Procedure Process_Default;
  482.    {This procedure handles the pressing of the "Default" button.
  483.     It restores the group icon to the Program Manager default and
  484.     removes any entry from the MYGROUPS.INI file}
  485.  
  486.    Var Icon:hIcon;
  487.  
  488.       Begin
  489.       Icon:=RemoveProp(Parent,Icon_Section);
  490.       If Icon <> pmIcon then
  491.          Begin
  492.          DestroyIcon(Icon);
  493.          WritePrivateProfileString(Icon_Section,IconRec.WindowText,Nil,Ini);
  494.          Icon:=pmIcon;
  495.          End;
  496.       SetProp(Parent,Icon_Section,Icon);
  497.       InvalidateRgn(Parent,0,True);
  498.       UpdateWindow(Parent);
  499.       End;
  500.  
  501.    Procedure Process_Program_Item(S:PChar);
  502.    {This procedure adds a program item, retrived via DDE from ProgMan
  503.     to the IconCol collection. First it looks for the icon specified
  504.     in the parameter line. If none is found, it looks at the
  505.     executable.
  506.  
  507.     Input:  S - A pointer to a string in the following format
  508.                 "Group name","Command line",path,Icon file,X coordinate,
  509.                 Y coordinate,Icon index,Hot Key,Minimized flag}
  510.  
  511.    Const Msg:Array [0..255] of Char = '';
  512.  
  513.    Var P1,P2:PChar;
  514.        I:Integer;
  515.        PIcon,OIcon:PIconRec;
  516.        Icon:hIcon;
  517.        Prog,Path:PChar;
  518.  
  519.       Begin
  520.       New(PIcon);
  521.       P1:=S;
  522.       P1:=StrScan(P1,',')+2;  {Skip comma and first quote}
  523.       P2:=P1+1;
  524.       While (P2^ <> ' ') and (P2^ <> '"') do {Skip until space or quote}
  525.          Inc(P2);
  526.       GetMem(Prog,StrDelta(P1,P2)+1);
  527.       StrLCopy(Prog,P1,StrDelta(P1,P2));  {Copy program name}
  528.       P2:=StrScan(P1,'"');    {Find next quote}
  529.       P1:=StrScan(P2,',')+1;   {Point to path}
  530.       P2:=StrScan(P1,',');
  531.       GetMem(Path,StrDelta(P1,P2)+1);
  532.       StrLCopy(Path,P1,StrDelta(P1,P2));  {Copy program path}
  533.       P1:=P2+1;     {Point to icon file}
  534.       P2:=StrScan(P1,',');
  535.       StrLCopy(Msg,P1,StrDelta(P1,P2));
  536.       If StrScan(Msg,'.') = Nil then
  537.          StrCat(Msg,'.EXE');
  538.       FileExpand(PIcon^.FileName,Msg);
  539.       StrCopy(PIcon^.WindowText,IconRec.WindowText);
  540.       P1:=StrScan(P2+1,',')+1; {Skip 2 more commas}
  541.       P1:=StrScan(P1,',')+1;
  542.       P2:=StrScan(P1,',');
  543.       PIcon^.Index:=StrVal(P1,StrDelta(P1,P2));
  544.       Icon:=ExtractIcon(hInstance,PIcon^.FileName,PIcon^.Index);
  545.       If Icon < 2 then
  546.          Begin {No icon...check executable}
  547.          If FindExecutable(Prog,Path,Msg) > 32 then
  548.             Begin
  549.             Icon:=ExtractIcon(hInstance,Msg,0);
  550.             If Icon > 1 then
  551.                Begin
  552.                FileExpand(PIcon^.FileName,Msg);
  553.                PIcon^.Index:=0;
  554.                End;
  555.             End;
  556.          End;
  557.       FreeMem(Prog,StrLen(Prog)+1);
  558.       FreeMem(Path,StrLen(Path)+1);
  559.       If Icon > 1 then
  560.          Begin
  561.          I:=IconCol^.Count-1;
  562.          While I >= 0 do     {We can't use an iterator method since}
  563.             Begin            {...it causes the stack to get too big}
  564.             OIcon:=IconCol^.At(I);
  565.             If (StrIComp(OIcon^.FileName,PIcon^.FileName) = 0) and
  566.                (OIcon^.Index = PIcon^.Index) then
  567.                I:=-1;
  568.             Dec(I);
  569.             End;
  570.          If I > -2 then
  571.             Begin
  572.             IconCol^.Insert(PIcon);
  573.             SendDlgItemMessage(Window,id_IconBar,lb_AddString,0,Word(Icon));
  574.             End
  575.          else
  576.             Begin
  577.             DestroyIcon(Icon);
  578.             Dispose(PIcon);
  579.             End;
  580.          End
  581.       else
  582.          Begin
  583.          StrCopy(Msg,'Cannot get icon from file'^M'"');
  584.          StrCat(Msg,PIcon^.FileName);
  585.          StrCat(Msg,'"');
  586.          MessageBox(0,Msg,'MyGroups Error',mb_IconExclamation or mb_OK);
  587.          Dispose(PIcon);
  588.          End;
  589.       End;
  590.  
  591.    Function Process_Programs:Boolean;
  592.    {This procedure handles the pressing of the "Programs" button.
  593.     It establishes a DDE conversation with the Program Manager and
  594.     gets the icons for the current group.
  595.     Returns TRUE if successful.}
  596.  
  597.    Var P,PGroup,PItem,PFile:PChar;
  598.        Len:LongInt;
  599.        Cursor:hCursor;
  600.  
  601.       Begin
  602.       Cursor:=SetCursor(LoadCursor(0,idc_Wait));
  603.       SendDlgItemMessage(Window,id_IconBar,lb_ResetContent,0,0);
  604.       Process_Programs:=False;
  605.       pmDDE:=New(PDDE,Init(Nil,cbf_Skip_AllNotifications or appcmd_ClientOnly));
  606.       If pmDDE <> Nil then
  607.          Begin
  608.          If pmDDE^.Connect('PROGMAN','PROGMAN') then
  609.             Begin
  610.             If (Lo(WinVer) = 3) and (Hi(WinVer) < 10) then
  611.                Begin
  612.                GetMem(PFile,256);
  613.                GetGroupName(IconRec.WindowText,PFile,256);
  614.                If PFile^ = #0 then
  615.                   P:=Nil
  616.                else
  617.                   P:=GetGroupDDE(PFile);
  618.                FreeMem(PFile,256);
  619.                End
  620.             else
  621.                P:=pmDDE^.Request(IconRec.WindowText,cf_Text,Len);
  622.             If P <> Nil then
  623.                Begin
  624.                New(IconCol,Init(40,10));
  625.                PGroup:=StrTok(P,^M);
  626.                PItem:=StrTok(Nil,^M)+1;
  627.                While StrLen(PItem) > 0 do
  628.                   Begin
  629.                   Process_Program_ITem(PItem);
  630.                   PItem:=StrTok(Nil,^M)+1;
  631.                   End;
  632.                If (Lo(WinVer) = 3) and (Hi(WinVer) < 10) then
  633.                   StrDispose(P)
  634.                else
  635.                   pmDDE^.FreeRequest;
  636.                If IconCol^.Count = 0 then
  637.                   Begin
  638.                   MessageBox(0,'No programs in group','MyGroups Error',
  639.                      mb_IconExclamation or mb_OK);
  640.                   Dispose(IconCol,Done);
  641.                   IconCol:=Nil;
  642.                   End
  643.                else
  644.                   Begin
  645.                   If (Lo(WinVer) = 3) and (Hi(WinVer) < 10) then
  646.                     Adjust_Win30;
  647.                   Process_Programs:=True;
  648.                   SendDlgItemMessage(Window,id_IconBar,lb_SetCurSel,0,0);
  649.                   SetWindowText(GetDlgItem(Window,id_File),'"Program Icons"');
  650.                   SetWindowText(GetDlgItem(Window,id_Programs),'&Prior File');
  651.                   SendDlgItemMessage(Window,id_File,em_SetModify,0,0);
  652.                   End;
  653.                End
  654.             else
  655.                MessageBox(0,'Cannot get programs','MyGroups Error',
  656.                   mb_IconExclamation or mb_OK);
  657.             pmDDE^.Disconnect;
  658.             End
  659.          else
  660.             MessageBox(0,'Cannot establish DDE with Program Manager',
  661.                'MyGroups Error',mb_IconExclamation or mb_OK);
  662.          Dispose(pmDDE,Done);
  663.          End
  664.       else
  665.          MessageBox(0,'Cannot initialize DDE interface','MyGroups Error',
  666.            mb_IconExclamation or mb_OK);
  667.       SetCursor(Cursor);
  668.       End;
  669.  
  670.    Begin
  671.    Icon_Dialog:=1;
  672.    Case Msg of
  673.       wm_InitDialog:
  674.          {Initialize the listbox to the proper size and load the icons
  675.           from the current file.}
  676.          Begin
  677.          IconCol:=Nil;
  678.          GetWindowRect(GetDlgItem(Window,id_IconBar),Rect);
  679.          SetWindowPos(GetDlgItem(Window,id_IconBar),0,0,0,
  680.             ((Rect.Right-Rect.Left) div (GetSystemMetrics(sm_cxIcon)+10)) *
  681.             (GetSystemMetrics(sm_cxIcon)+10),
  682.             GetSystemMetrics(sm_cyIcon)+4+GetSystemMetrics(sm_cyHScroll),
  683.             swp_noZOrder or swp_NoMove);
  684.          Parent:=lParam;
  685.          GetIconData(Parent,IconRec);
  686.          LoadIcons(IconRec.FileName);
  687.          SendDlgItemMessage(Window,id_IconBar,lb_SetCurSel,
  688.            IconRec.Index,0);
  689.          SendDlgItemMessage(Window,id_IconBar,lb_SetColumnWidth,
  690.            (GetSystemMetrics(sm_cxIcon)+10),0);
  691.          SetWindowText(Window,IconRec.WindowText);
  692.          End;
  693.       wm_Destroy:
  694.          {Finished with the dialog. Dispose of the collection if necessary}
  695.          If IconCol <> Nil then
  696.             Dispose(IconCol,Done);
  697.       wm_DrawItem:
  698.          {Draw the "Current icon" and the icons in the icon box}
  699.          With PDrawItemStruct(lParam)^ do
  700.             If CtlID = id_IconBar then
  701.                 If (ItemAction = oda_DrawEntire) or
  702.                    (ItemAction = oda_Select) then
  703.                    Begin
  704.                    J:=SetMapMode(hDC,mm_Text);
  705.                    If (ItemState and ods_Selected) = ods_Selected then
  706.                       Brush:=SelectObject(hDC,CreateSolidBrush(
  707.                          GetSysColor(COLOR_HIGHLIGHT)))
  708.                    else
  709.                       Brush:=SelectObject(hDC,CreateSolidBrush(
  710.                          GetSysColor(COLOR_WINDOW)));
  711.                    PatBlt(hDC,rcItem.Left,rcItem.Top,
  712.                       rcItem.Right-rcItem.Left,
  713.                       rcItem.Bottom-rcItem.Top,
  714.                       PatCopy);
  715.                    DrawIcon(hDC,rcItem.Left+5,rcItem.Top+2,
  716.                       LoWord(itemData));
  717.                    DeleteObject(SelectObject(hDC,Brush));
  718.                    SetMapMode(hDC,J);
  719.                    End
  720.                 else
  721.              else If CtlID = id_Icon then
  722.                Begin
  723.                J:=SetMapMode(hDC,mm_Text);
  724.                Brush:=SelectObject(hDC,CreateSolidBrush(
  725.                   GetSysColor(COLOR_WINDOW)));
  726.                PatBlt(hDC,rcItem.Left,rcItem.Top,
  727.                   rcItem.Right-rcItem.Left,
  728.                   rcItem.Bottom-rcItem.Top,
  729.                   PatCopy);
  730.                DrawIcon(hDC,0,0,GetProp(Parent,Icon_Section));
  731.                DeleteObject(SelectObject(hDC,Brush));
  732.                SetMapMode(hDC,J);
  733.                End;
  734.       wm_Command:
  735.          Case wParam of
  736.             id_OK: {OK button pressed}
  737.                If Process_OK(True) then
  738.                   EndDialog(Window,1);
  739.             id_Cancel: {Cancel button pressed}
  740.                EndDialog(Window,0);
  741.             id_IconBar: {Notification messages for the listbox}
  742.                Case HiWord(lParam) of
  743.                   lbn_DblClk: {An icon was double clicked}
  744.                      If Process_OK(True) then
  745.                         EndDialog(Window,1);
  746.                   lbn_SetFocus: {Focus changed...see if we need to load icons}
  747.                      Process_OK(False);
  748.                   else
  749.                      Icon_Dialog:=0;
  750.                   End;
  751.             id_Browse:   {Browse button pressed}
  752.                Process_Browse;
  753.             id_Default:  {Default button pressed}
  754.                Begin
  755.                Process_Default;
  756.                EndDialog(Window,1);
  757.                End;
  758.             id_Programs: {Programs button pressed}
  759.                If (IconCol <> Nil) or not Process_Programs then
  760.                   Begin
  761.                   SetWindowText(GetDlgItem(Window,id_File),OldFile);
  762.                   SendDlgItemMessage(Window,id_File,em_SetModify,1,0);
  763.                   Process_OK(False);
  764.                   End;
  765.             id_Icon:     {"Current icon" pressed}
  766.                Begin
  767.                If (($8000) and GetKeyState(vk_Shift) and
  768.                  GetKeyState(vk_Control) and GetKeyState(vk_Menu)) <> 0 then
  769.                  DialogBoxParam(hInstance,'Copyright',Window,@Copyright_Dlg,1);
  770.                End;
  771.             else
  772.                Icon_Dialog:=0;
  773.             End;
  774.       wm_MeasureItem:  {Set the height of the icons in the listbox}
  775.          With PMeasureItemStruct(lParam)^ do
  776.             If CtlID = id_IconBar then
  777.                itemHeight:=GetSystemMetrics(sm_cyIcon)+4;
  778.       wm_DeleteItem:
  779.          {An icon in the listbox is being deleted...destroy the icon}
  780.          With PDeleteItemStruct(lParam)^ do
  781.             If CtlID = id_IconBar then
  782.                DestroyIcon(LoWord(itemData));
  783.       else
  784.          Icon_Dialog:=0;
  785.       End;
  786.    End;
  787.  
  788. Function PMProc(Window:hWnd; Msg,wParam:Word; lParam:LongInt):LongInt;
  789. {This is the new window function for the Program Manager main window.
  790.  We need to deal with a special case here. If the current group window
  791.  is maximized, and the user selects one of our new menu items, the
  792.  wm_SysCommand and wm_InitMenu messagees are not posted to the child window.
  793.  Instead a wm_Command and wm_InitMenu are posted to the frame window.
  794.  This function intercepts those messages and posts the expected messages
  795.  to the MDI child.
  796.  Always returns the result of the default window function.
  797.  
  798.  Input - Standard window function parameters}
  799.  
  800. Var MDIActive:LongInt;
  801.  
  802.    Begin
  803.    Case Msg of
  804.       wm_Command:
  805.          Begin
  806.          If ((wParam and $FFF0 = cm_ChangeIcon) or (wParam and $FFF0 = cm_UnloadProg)) and
  807.             (LoWord(lParam) = 0) then
  808.             Begin
  809.             MDIActive:=SendMessage(MDIClient,wm_MDIGetActive,0,0);
  810.             If HiWord(MDIActive) = 1 then {Maximized}
  811.                PostMessage(LoWord(MDIActive),wm_SysCommand,wParam,0);
  812.             PMProc:=0;
  813.             End;
  814.          End;
  815.       wm_InitMenu:
  816.          Begin
  817.          MDIActive:=SendMessage(MDIClient,wm_MDIGetActive,0,0);
  818.          If HiWord(MDIActive) = 1 then  {Maximized}
  819.             SendMessage(LoWord(MDIActive),wm_InitMenu,wParam,lParam);
  820.          End;
  821.       End;
  822.    PMProc:=CallWindowProc(oldPMProc,Window,Msg,wParam,lParam);
  823.    End;
  824.  
  825. Function GroupProc(Window:hWnd; Msg,wParam:Word; lParam:LongInt):Longint;
  826. {This is the new window function for the group windows. It handles all
  827.  messages needed to draw the new "custom" group icons.
  828.  Returns the result of the default group window function.
  829.  
  830.  Input:  The standard window function parameters}
  831.  
  832. Const Labl:Array [0..255] of Char = '';
  833.       IconRec:TIconRec = (FileName:''; WindowText:''; Index:0);
  834.  
  835. Var DC:hDC;
  836.     PS:TPaintStruct;
  837.     MapMode:Integer;
  838.     Brush:hBrush;
  839.     Menu:hMenu;
  840.     Temp:Array [0..10] of Char;
  841.     Origin:TPoint;
  842.     Icon:hIcon;
  843.     Rect:TRect;
  844.  
  845.    Begin
  846.    Case Msg of
  847.       wm_Paint: {If the group is minimized, then draw the new icon}
  848.          Begin
  849.          If IsIconic(Window) then
  850.             Begin
  851.             DC:=BeginPaint(Window,PS);
  852.             DrawIcon(DC,2,2,GetProp(Window,Icon_Section));
  853.             EndPaint(Window,PS);
  854.             GroupProc:=1;
  855.             End
  856.          else
  857.             GroupProc:=CallWindowProc(oldGroupProc,Window,Msg,wParam,lParam);
  858.          End;
  859.       wm_EraseBkGnd:
  860.          {Erase the background of the minimized group to match the rest
  861.           of the Program Manager workspace}
  862.          If IsIconic(Window) then
  863.             Begin
  864.             GetClipBox(wParam,Rect);
  865.             Brush:=CreateSolidBrush(GetSysColor(COLOR_APPWORKSPACE));
  866.             UnRealizeObject(Brush);
  867.             LongInt(Origin):=0;
  868.             ClientToScreen(GetParent(Window),Origin);
  869.             SetBrushOrg(wParam,Origin.X,Origin.Y);
  870.             Brush:=SelectObject(wParam,Brush);
  871.             PatBlt(wParam,Rect.Left,Rect.Top,
  872.                Rect.Right-Rect.Left,
  873.                Rect.Bottom-Rect.Top,PatCopy);
  874.             DeleteObject(SelectObject(wParam,Brush));
  875.             GroupProc:=1;
  876.             End
  877.          else
  878.             GroupProc:=CallWindowProc(oldGroupProc,Window,Msg,wParam,lParam);
  879.       wm_QueryDragIcon:
  880.          {The user is dragging a group icon. Return the handle to the new
  881.           icon so that the dragged icon displays properly.}
  882.          Begin
  883.          GroupProc:=GetProp(Window,Icon_Section);
  884.          End;
  885.       wm_SysCommand: {User selected a group menu command}
  886.          Case (wParam and $FFF0) of
  887.             cm_ChangeIcon:  {Open "Change Icon" dialog box}
  888.                Begin
  889.                DialogBoxParam(hInstance,'CHANGE_ICON',Window,@Icon_Dialog,
  890.                   Window);
  891.                GroupProc:=1;
  892.                End;
  893.             cm_UnloadProg:  {Unload MyGroups}
  894.                Begin
  895.                If MessageBox(Window,'Are you sure you want to unload MyGroups?',
  896.                   'Unload MyGroups',mb_IconQuestion or mb_YESNO) = id_Yes then
  897.                   Begin
  898.                   EnumWindows(@EnumProc,1);
  899.                   InvalidateRect(0,Nil,True);
  900.                   While GetModuleUsage(MyModule) > 1 do
  901.                      FreeLibrary(MyModule);
  902.                   {We can't call FreeLibrary for the last instance of the
  903.                    module since the code won't be here for us to return
  904.                    to! Instead we fix up the stack to return to the code
  905.                    that called us and JUMP to FreeLibrary.}
  906.                      Asm
  907.                      MOV   DX,[MyModule]
  908.                      POP   DI           {Restore DI and SI}
  909.                      POP   SI
  910.                      LEA   SP,[BP-2]    {Remove locals from stack}
  911.                      POP   DS           {Restore DS and BP}
  912.                      POP   BP
  913.                      DEC   BP
  914.                      POP   AX           {Save return address}
  915.                      POP   BX
  916.                      ADD   SP,$0A       {Remove parameters from stack}
  917.                      PUSH  DX           {Push module ID}
  918.                      PUSH  BX           {Push return address}
  919.                      PUSH  AX
  920.                      JMP   FreeLibrary  {Unload MyGroups}
  921.                      End;               {We never return from this}
  922.                   End;
  923.                GroupProc:=1;
  924.                End;
  925.             else
  926.                GroupProc:=CallWindowProc(oldGroupProc,Window,Msg,wParam,lParam);
  927.             End;
  928.       wm_Create: {User is creating a new program group}
  929.          Begin
  930.          GroupProc:=CallWindowProc(oldGroupProc,Window,Msg,wParam,lParam);
  931.          SetProp(Window,Icon_Section,pmIcon);
  932.          SetProp(Window,Menu_Section,0);
  933.          End;
  934.       wm_InitMenu: {User is selecting the group system menu}
  935.          Begin
  936.          If GetProp(Window,Menu_Section) = 0 then
  937.             Begin
  938.             Menu:=GetSystemMenu(Window,False);
  939.             AppendMenu(Menu,mf_Separator,0,Nil);
  940.             AppendMenu(Menu,mf_String or mf_Enabled,cm_ChangeIcon,
  941.               'Change &Icon');
  942.             AppendMenu(Menu,mf_String or mf_Enabled,cm_UnloadProg,
  943.               '&Unload MyGroups');
  944.             SetProp(Window,Menu_Section,1);
  945.             End;
  946.          GroupProc:=CallWindowProc(oldGroupProc,Window,Msg,wParam,lParam);
  947.          End;
  948.       wm_Destroy:  {User is deleting a program group}
  949.          Begin
  950.          Icon:=RemoveProp(Window,Icon_Section);
  951.          If Icon <> pmIcon then
  952.             Begin
  953.             DestroyIcon(Icon);
  954.             GetWindowText(Window,Labl,Sizeof(Labl));
  955.             WritePrivateProfileString(Icon_Section,Labl,Nil,Ini);
  956.             End;
  957.          GroupProc:=CallWindowProc(oldGroupProc,Window,Msg,wParam,lParam);
  958.          End;
  959.       wm_SetText:  {User is changing the group description}
  960.          Begin
  961.          Icon:=GetProp(Window,Icon_Section);
  962.          If Icon <> pmIcon then
  963.             Begin
  964.             GetIconData(Window,IconRec);
  965.             WritePrivateProfileString(Icon_Section,IconRec.WindowText,
  966.                Nil,Ini);
  967.             StrCopy(IconRec.WindowText,PStr(lParam));
  968.             PutIconData(IconRec);
  969.             End;
  970.          GroupProc:=CallWindowProc(oldGroupProc,Window,Msg,wParam,lParam);
  971.          End;
  972.       else
  973.          GroupProc:=CallWindowProc(oldGroupProc,Window,Msg,wParam,lParam);
  974.       End;
  975.    End;
  976.  
  977. Procedure CheckIni;
  978. {This procedure checks to see if there are any entries in MYGROUPS.INI
  979.  which do not have any matching program groups. If so, the user is
  980.  prompted to delete the entry.}
  981.  
  982. Var Msg:Array [0..255] of Char;
  983.  
  984.    Procedure Warn(Item:PChar); Far;
  985.  
  986.       Begin
  987.       StrCopy(Msg,'Warning: Group "');
  988.       StrCat(Msg,Item);
  989.       StrCat(Msg,'" not found.'^M'Delete entry in MYGROUPS.INI?');
  990.       If MessageBox(0,Msg,'MyGroups',mb_IconQuestion or mb_YesNo) = id_Yes then
  991.          WritePrivateProfileString(Icon_Section,Item,Nil,Ini);
  992.       End;
  993.  
  994.    Begin
  995.    If Warn_Grp then Collection^.ForEach(@Warn);
  996.    Dispose(Collection,Done);
  997.    End;
  998.  
  999. Procedure Timer(Window:hWnd; Msg,idTimer:Word; dwTime:LongInt); Export;
  1000. {This timer is a fix for Windows 3.0. Since the RUN= line in WIN.INI
  1001.  is processed before the groups are created (the order is reversed in
  1002.  Windows 3.1) we need to periodically "poll" to see if we can now
  1003.  subclass the groups.
  1004.  
  1005.  Also, kill the copyright dialog if initialization took less than 2 seconds}
  1006.  
  1007.    Begin
  1008.    If idTimer = 1 then
  1009.       If Window = CopyRight then
  1010.          Begin  {kill copyright}
  1011.          KillTimer(Window,idTimer);
  1012.          DestroyWindow(Window);
  1013.          CopyRight:=0;
  1014.          Exit;
  1015.          End
  1016.       else
  1017.    else
  1018.    If not DidSubClass then
  1019.       Begin
  1020.       EnumWindows(@EnumProc,0);
  1021.       If DidSubClass then
  1022.          Begin
  1023.          KillTimer(Window,idTimer);
  1024.          CheckIni;
  1025.          End;
  1026.       End;
  1027.    End;
  1028.  
  1029. Var Result:Boolean;
  1030.     PIni:PChar;
  1031.     TickCount:LongInt;
  1032.     PM_Mod:THandle;
  1033.  
  1034. {Initialization code.
  1035.   1. Make sure we are not in Windows 3.0 real mode.
  1036.   2. Unlock the data segment.
  1037.   3. Display the copyright notice.
  1038.   4. Get INI file settings
  1039.   5. Find the Program Manager.
  1040.   6. Subclass all the program groups.}
  1041.  
  1042. Begin
  1043. If (GetWinFlags and wf_PMODE) = 0 then
  1044.    Begin
  1045.    MessageBox(0,'This program cannot run in real mode','MyGroups',
  1046.       mb_IconStop or mb_OK);
  1047.    ExitCode:=0;
  1048.    Exit;
  1049.    End;
  1050. GlobalPageUnlock(DSeg);
  1051. GlobalRealloc(LOWORD(GlobalHandle(DSeg)),0,GMEM_MODIFY or GMEM_MOVEABLE);
  1052. {$IFNDEF VER70}
  1053. HeapLimit:=1024;   {Enable subheap allocation for TPW 1.5}
  1054. {$ENDIF}
  1055. WinVer:=GetVersion;
  1056. CopyRight:=CreateDialogParam(hInstance,'CopyRight',0,@CopyRight_Dlg,0);
  1057. TickCount:=GetTickCount;
  1058. Warn_Icon:=Boolean(GetPrivateProfileInt(Warnings,WarnIcon,1,Ini));
  1059. Warn_Grp:=Boolean(GetPrivateProfileInt(Warnings,WarnGrp,1,Ini));
  1060. Str(Byte(Warn_Icon),Myself);
  1061. WritePrivateProfileString(Warnings,WarnIcon,Myself,Ini);
  1062. Str(Byte(Warn_Grp),Myself);
  1063. WritePrivateProfileString(Warnings,WarnGrp,Myself,Ini);
  1064. StrPCopy(Myself,ParamStr(0));
  1065. MyModule:=GetModuleHandle(Myself);
  1066. PM_Mod:=GetModuleHandle('PROGMAN');
  1067. If PM_Mod = 0 then
  1068.    Begin
  1069.    DestroyWindow(CopyRight);
  1070.    MessageBox(0,'Cannot locate Program Manager','MyGroups',
  1071.       mb_IconStop or mb_OK);
  1072.    ExitCode:=0;
  1073.    Exit;
  1074.    End;
  1075. IniSize:=1000;
  1076. GetMem(IniGroups,IniSize);
  1077. While GetPrivateProfileString(Icon_Section,Nil,'',IniGroups,IniSize,Ini) =
  1078.    IniSize-1 do
  1079.    Begin
  1080.    FreeMem(IniGroups,IniSize);
  1081.    Inc(IniSize,500);
  1082.    GetMem(IniGroups,IniSize);
  1083.    End;
  1084. PIni:=IniGroups;
  1085. New(Collection,Init(30,10));
  1086. While PIni^ <> #0 do
  1087.    Begin
  1088.    Collection^.Insert(StrNew(PIni));
  1089.    Inc(PIni,StrLen(PIni)+1);
  1090.    End;
  1091. FreeMem(IniGroups,IniSize);
  1092. GetModuleFileName(PM_Mod,ProgMan,Sizeof(ProgMan));
  1093. EnumWindows(@EnumProc,0);
  1094. TickCount:=GetTickCount-TickCount;
  1095. If TickCount >= 2000 then
  1096.    DestroyWindow(CopyRight)
  1097. else
  1098.    SetTimer(CopyRight,1,2000-TickCount,@Timer);
  1099. If DidSubClass then
  1100.    CheckIni
  1101. else
  1102.    SetTimer(0,2,500,@Timer);  {fix for Windows 3.0}
  1103. End.
  1104.